#include <assert.h>
#include <string.h>
#include "link.h"

#include "common.h"
#include "data_channel.h"
#include "ctrl_channel.h"

static void accept_connection(link_t* remote, void* arg, int* stop);

extern logger_t log;

int data_setup_master(link_t* ctrl, link_t* data, int mode, on_handshaked fp_handshaked, void* arg)
{
	ctrl_message msg, reply;

	assert(ctrl != NULL);
	assert(data != NULL);

	if (ctrl == NULL || data == NULL) {
		return DATA_ERROR;
	}

	if (mode == ACTIVE) {
		link_t local;

		DPRINT("[dc] entering active mode...");

		local.port = 0;

		if (link_listen(&local, 1) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (couldn't listen on socket)");
			return DATA_ERROR;
		}

		msg.ctrl = PORT;
		msg.port = local.port;
		if (ctrl_send_receive(ctrl, &msg, &reply) != CTRL_SUCCESS || reply.ctrl != PORT_ACK) {
			DPRINT("[dc] FAILED (couldn't notify port or receive PORT_ACK)");
			link_close(&local);
			return DATA_ERROR;
		}

		DPRINT("[dc] sent PORT, received 200");

		if (fp_handshaked != NULL) {
			int abort = 0;

			fp_handshaked(ctrl, ACTIVE, arg, &abort);

			if (abort) {
				DPRINT("[dc] ABORTED");
				link_close(&local);
				return DATA_ABORTED;
			}
		}

		if (link_accept(&local, accept_connection, data) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (couldn't accept a new connection)");
			return DATA_ERROR;
		}

		DPRINT("[dc] OK");
	}
	else if (mode == PASSIVE) {
		DPRINT("[dc] entering passive mode...");

		msg.ctrl = PASV;
		if (ctrl_send_receive(ctrl, &msg, &reply) != CTRL_SUCCESS || reply.ctrl != PASV_ACK) {
			DPRINT("[dc] FAILED (couldn't send PASV or receive PASV_ACK");
			return DATA_ERROR;
		}

		if (fp_handshaked != NULL) {
			int abort = 0;

			fp_handshaked(ctrl, PASSIVE, arg, &abort);

			if (abort) {
				DPRINT("[dc] ABORTED");
				return DATA_ABORTED;
			}
		}

		data->addr = ctrl->addr;
		data->port = reply.port;
		if (link_connect(data) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (could not connect to remote data link)");
			return DATA_ERROR;
		}

		DPRINT("[dc] OK");
	}
	else {
		assert(0);
	}

	return DATA_SUCCESS;
}

int data_setup_slave(link_t* ctrl, link_t* data, on_handshaked fp_handshaked, void* arg)
{
	ctrl_message msg, reply;
	int crc;

	assert(ctrl != NULL);
	assert(data != NULL);

	if (ctrl == NULL || data == NULL) {
		return DATA_ERROR;
	}

	/* receive the working mode */
	crc = ctrl_send_receive(ctrl, NULL, &reply);
	if (crc != CTRL_SUCCESS) {
		if (crc == CTRL_PARSE_ERROR) {
			DPRINT("[dc] received unrecognized command, sending SYNTAX_ERROR");
			msg.ctrl = SYNTAX_ERROR;
			if (ctrl_send_receive(ctrl, &msg, NULL) != CTRL_SUCCESS) {
				DPRINT("[dc] failed to send ERR_NO_ACTION");
				return DATA_ERROR;
			}

			return DATA_ABORTED;
		}
		else {
			DPRINT("[dc] couldn't receive the working mode");
			return DATA_ERROR;
		}
	}

	if (reply.ctrl == PORT) { /* ACTIVE */
		msg.ctrl = PORT_ACK;
		if (ctrl_send_receive(ctrl, &msg, NULL) != CTRL_SUCCESS) {
			DPRINT("[dc] FAILED (could not send PORT_ACK)");
			return DATA_ERROR;
		}

		DPRINT("[dc] entering active mode...");

		if (fp_handshaked != NULL) {
			int abort = 0;

			fp_handshaked(ctrl, ACTIVE, arg, &abort);

			if (abort) {
				DPRINT("[dc] ABORTED");
				return DATA_ABORTED;
			}
		}

		data->addr = ctrl->addr;
		data->port = reply.port;
		if (link_connect(data) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (could not connect to remote data link)");
			return DATA_ERROR;
		}

		DPRINT("[dc] OK");
	}
	else if (reply.ctrl == PASV) { /* PASSIVE */
		link_t local;

		DPRINT("[dc] entering passive mode...");

		local.port = 0;
		if (link_listen(&local, 1) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (couldn't listen on socket)");
			return DATA_ERROR;
		}

		msg.ctrl = PASV_ACK;
		msg.port = local.port;
		if (ctrl_send_receive(ctrl, &msg, NULL) != CTRL_SUCCESS) {
			DPRINT("[dc] FAILED (couldn't notify port)");
			link_close(&local);
			return DATA_ERROR;
		}

		if (fp_handshaked != NULL) {
			int abort = 0;

			fp_handshaked(ctrl, PASSIVE, arg, &abort);

			if (abort) {
				DPRINT("[dc] ABORTED");
				link_close(&local);
				return DATA_ABORTED;
			}
		}

		if (link_accept(&local, accept_connection, data) != LINK_SUCCESS) {
			DPRINT("[dc] FAILED (couldn't accept a new connection)");
			return DATA_ERROR;
		}

		DPRINT("[dc] OK");
	}
	else if (reply.ctrl == STOR || reply.ctrl == RETR) { /* STOR/RETR */
		DPRINT("[dc] received STOR/RETR before PORT/PASV");

		msg.ctrl = ERR_NO_ACTION;
		if (ctrl_send_receive(ctrl, &msg, NULL) != CTRL_SUCCESS) {
			DPRINT("[dc] failed to send ERR_NO_ACTION");
			return DATA_ERROR;
		}

		return DATA_ABORTED;
	}
	else {
		DPRINT("[dc] did not receive a correct working mode = %d", reply.ctrl);
		return DATA_ERROR;
	}

	return DATA_SUCCESS;
}

int data_send(link_t* data, char* buffer, int size)
{
	DPRINT("[dc] sending %d bytes ...", size);

	if (link_send_raw(data, buffer, size) != LINK_SUCCESS) {
		DPRINT("[dc] FAILED (error in link_send_raw)");
		return DATA_ERROR;
	}

	DPRINT("[dc] OK");

	return DATA_SUCCESS;
}

int data_receive(link_t* data, char* buffer, int size, int* received)
{
	int temp, lrc;

	DPRINT("[dc] receiving ...");

	lrc = link_receive_raw(data, buffer, size, &temp);

	if (lrc == LINK_SUCCESS) {
		DPRINT("[dc] OK (received %d bytes)", temp);

		if (received != NULL) {
			*received = temp;
		}

		return DATA_SUCCESS;
	}
	else if (lrc == LINK_DISCONNECTED) {
		DPRINT("[dc] ABORTED (remote host disconnected)");
		return DATA_DISCONNECTED;
	}

	DPRINT("[dc] FAILED (error in link_receive_raw)");

	return DATA_ERROR;
}

static void accept_connection(link_t* remote, void* arg, int* abort)
{
	link_t* data = (link_t *) arg;
	memcpy(data, remote, sizeof(link_t));
	*abort = 1; /* we need to accept only a single connection */
}

